/*
 * Remmina - The GTK+ Remote Desktop Client
 * Copyright (C) 2010-2011 Vic Lee
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA 02111-1307, USA.
 */

#include "common/remminaplugincommon.h"

#define REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY            1
#define REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY           2
#define REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT 3
#define REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH            4
#define REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT               5
#define REMMINA_PLUGIN_VNC_FEATURE_SCALE                   6
#define REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS                 7

typedef struct _RemminaPluginVncData
{
    /* Whether the user requests to connect/disconnect */
    gboolean connected;
    /* Whether the vnc process is running */
    gboolean running;
    /* Whether the initialzation calls the authentication process */
    gboolean auth_called;
    /* Whether it is the first attempt for authentication. Only first attempt will try to use cached credentials */
    gboolean auth_first;

    GtkWidget *drawing_area;
    guchar *vnc_buffer;
    GdkPixbuf *rgb_buffer;

    GdkPixbuf *scale_buffer;
    gint scale_width;
    gint scale_height;
    guint scale_handler;

    gint queuedraw_x, queuedraw_y, queuedraw_w, queuedraw_h;
    guint queuedraw_handler;

    gulong clipboard_handler;
    GTimeVal clipboard_timer;

    GdkPixbuf *queuecursor_pixbuf;
    gint queuecursor_x, queuecursor_y;
    guint queuecursor_handler;

    gpointer client;
    gint listen_sock;

    gint button_mask;

    GPtrArray *pressed_keys;

    GQueue *vnc_event_queue;
    gint vnc_event_pipe[2];

#ifdef HAVE_PTHREAD
    pthread_t thread;
    pthread_mutex_t buffer_mutex;
#else
    gint thread;
#endif
} RemminaPluginVncData;

static RemminaPluginService *remmina_plugin_service = NULL;

static int dot_cursor_x_hot = 2;
static int dot_cursor_y_hot = 2;
static const gchar * dot_cursor_xpm[] = {
"5 5 3 1",
" 	c None",
".	c #000000",
"+	c #FFFFFF",
" ... ",
".+++.",
".+ +.",
".+++.",
" ... "};

#ifdef HAVE_PTHREAD
#define LOCK_BUFFER(t)      if(t){CANCEL_DEFER}pthread_mutex_lock(&gpdata->buffer_mutex);
#define UNLOCK_BUFFER(t)    pthread_mutex_unlock(&gpdata->buffer_mutex);if(t){CANCEL_ASYNC}
#else
#define LOCK_BUFFER(t)
#define UNLOCK_BUFFER(t)
#endif

enum
{
    REMMINA_PLUGIN_VNC_EVENT_KEY,
    REMMINA_PLUGIN_VNC_EVENT_POINTER,
    REMMINA_PLUGIN_VNC_EVENT_CUTTEXT,
    REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN,
    REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND,
    REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE
};

typedef struct _RemminaPluginVncEvent
{
    gint event_type;
    union
    {
        struct
        {
            guint keyval;
            gboolean pressed;
        } key;
        struct
        {
            gint x;
            gint y;
            gint button_mask;
        } pointer;
        struct
        {
            gchar *text;
        } text;
    } event_data;
} RemminaPluginVncEvent;

static void
remmina_plugin_vnc_event_push (RemminaProtocolWidget *gp, gint event_type, gpointer p1, gpointer p2, gpointer p3)
{
    RemminaPluginVncData *gpdata;
    RemminaPluginVncEvent *event;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    event = g_new (RemminaPluginVncEvent, 1);
    event->event_type = event_type;
    switch (event_type)
    {
    case REMMINA_PLUGIN_VNC_EVENT_KEY:
        event->event_data.key.keyval = GPOINTER_TO_UINT (p1);
        event->event_data.key.pressed = GPOINTER_TO_INT (p2);
        break;
    case REMMINA_PLUGIN_VNC_EVENT_POINTER:
        event->event_data.pointer.x = GPOINTER_TO_INT (p1);
        event->event_data.pointer.y = GPOINTER_TO_INT (p2);
        event->event_data.pointer.button_mask = GPOINTER_TO_INT (p3);
        break;
    case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
    case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
        event->event_data.text.text = g_strdup ((char*) p1);
        break;
    default:
        break;
    }
    g_queue_push_tail (gpdata->vnc_event_queue, event);
    if (write (gpdata->vnc_event_pipe[1], "\0", 1))
    {
        /* Ignore */
    }
}

static void
remmina_plugin_vnc_event_free (RemminaPluginVncEvent *event)
{
    switch (event->event_type)
    {
    case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
    case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
        g_free (event->event_data.text.text);
        break;
    default:
        break;
    }
    g_free (event);
}

static void
remmina_plugin_vnc_event_free_all (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    RemminaPluginVncEvent *event;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    while ((event = g_queue_pop_head (gpdata->vnc_event_queue)) != NULL)
    {
        remmina_plugin_vnc_event_free (event);
    }
}

static void
remmina_plugin_vnc_scale_area (RemminaProtocolWidget *gp, gint *x, gint *y, gint *w, gint *h)
{
    RemminaPluginVncData *gpdata;
    gint sx, sy, sw, sh;
    gint width, height;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (gpdata->rgb_buffer == NULL || gpdata->scale_buffer == NULL) return;

    width = remmina_plugin_service->protocol_plugin_get_width (gp);
    height = remmina_plugin_service->protocol_plugin_get_height (gp);

    if (gpdata->scale_width == width && gpdata->scale_height == height)
    {
        /* Same size, just copy the pixels */
        gdk_pixbuf_copy_area (gpdata->rgb_buffer, *x, *y, *w, *h, gpdata->scale_buffer, *x, *y);
        return;
    }

    /* We have to extend the scaled region 2 scaled pixels, to avoid gaps */
    sx = MIN (MAX (0, (*x) * gpdata->scale_width / width
        - gpdata->scale_width / width - 2), gpdata->scale_width - 1);
    sy = MIN (MAX (0, (*y) * gpdata->scale_height / height
        - gpdata->scale_height / height - 2), gpdata->scale_height - 1);
    sw = MIN (gpdata->scale_width - sx, (*w) * gpdata->scale_width / width
        + gpdata->scale_width / width + 4);
    sh = MIN (gpdata->scale_height - sy, (*h) * gpdata->scale_height / height
        + gpdata->scale_height / height + 4);

    gdk_pixbuf_scale (gpdata->rgb_buffer, gpdata->scale_buffer,
        sx, sy,
        sw, sh,
        0, 0,
        (double) gpdata->scale_width / (double) width,
        (double) gpdata->scale_height / (double) height,
        remmina_plugin_service->pref_get_scale_quality ());

    *x = sx; *y = sy; *w = sw; *h = sh;
}

static gboolean
remmina_plugin_vnc_update_scale_buffer (RemminaProtocolWidget *gp, gboolean in_thread)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    gint width, height;
    gint gpwidth, gpheight;
    gint hscale, vscale;
    gboolean scale;
    gint x, y, w, h;
    GdkPixbuf *pixbuf;
    GtkAllocation a;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);

    if (gpdata->running)
    {
        gtk_widget_get_allocation (GTK_WIDGET (gp), &a);
        width = a.width;
        height = a.height;
        scale = remmina_plugin_service->protocol_plugin_get_scale (gp);
        if (scale)
        {
            if (width > 1 && height > 1)
            {
                LOCK_BUFFER (in_thread)

                if (gpdata->scale_buffer)
                {
                    g_object_unref (gpdata->scale_buffer);
                }
                gpwidth = remmina_plugin_service->protocol_plugin_get_width (gp);
                gpheight = remmina_plugin_service->protocol_plugin_get_height (gp);
                hscale = remmina_plugin_service->file_get_int (remminafile, "hscale", 0);
                vscale = remmina_plugin_service->file_get_int (remminafile, "vscale", 0);
                gpdata->scale_width = (hscale > 0 ?
                    MAX (1, gpwidth * hscale / 100) : width);
                gpdata->scale_height = (vscale > 0 ?
                    MAX (1, gpheight * vscale / 100) : height);

                pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
                    gpdata->scale_width, gpdata->scale_height);
                gpdata->scale_buffer = pixbuf;

                x = 0; y = 0; w = gpwidth; h = gpheight;
                remmina_plugin_vnc_scale_area (gp, &x, &y, &w, &h);

                UNLOCK_BUFFER (in_thread)
            }
        }
        else
        {
            LOCK_BUFFER (in_thread)

            if (gpdata->scale_buffer)
            {
                g_object_unref (gpdata->scale_buffer);
                gpdata->scale_buffer = NULL;
            }
            gpdata->scale_width = 0;
            gpdata->scale_height = 0;

            UNLOCK_BUFFER (in_thread)
        }
        if (width > 1 && height > 1)
        {
            if (in_thread)
            {
                THREADS_ENTER
                gtk_widget_queue_draw_area (GTK_WIDGET (gp), 0, 0, width, height);
                THREADS_LEAVE
            }
            else
            {
                gtk_widget_queue_draw_area (GTK_WIDGET (gp), 0, 0, width, height);
            }
        }
    }
    gpdata->scale_handler = 0;
    return FALSE;
}

static gboolean
remmina_plugin_vnc_update_scale_buffer_main (RemminaProtocolWidget *gp)
{
    return remmina_plugin_vnc_update_scale_buffer (gp, FALSE);
}

static void
remmina_plugin_vnc_update_scale (RemminaProtocolWidget *gp, gboolean scale)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    gint width, height;
    gint hscale, vscale;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);

    width = remmina_plugin_service->protocol_plugin_get_width (gp);
    height = remmina_plugin_service->protocol_plugin_get_height (gp);
    if (scale)
    {
        hscale = remmina_plugin_service->file_get_int (remminafile, "hscale", 0);
        vscale = remmina_plugin_service->file_get_int (remminafile, "vscale", 0);
        gtk_widget_set_size_request (GTK_WIDGET (gpdata->drawing_area),
            (hscale > 0 ? width * hscale / 100 : -1),
            (vscale > 0 ? height * vscale / 100 : -1));
    }
    else
    {
        gtk_widget_set_size_request (GTK_WIDGET (gpdata->drawing_area), width, height);
    }
    remmina_plugin_service->protocol_plugin_emit_signal (gp, "update-align");
}

gboolean
remmina_plugin_vnc_setcursor (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    GdkCursor *cur;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    LOCK_BUFFER (FALSE)
    gpdata->queuecursor_handler = 0;

    if (gpdata->queuecursor_pixbuf)
    {
        cur = gdk_cursor_new_from_pixbuf (gdk_display_get_default (),
            gpdata->queuecursor_pixbuf, gpdata->queuecursor_x, gpdata->queuecursor_y);
        gdk_window_set_cursor (gtk_widget_get_window (gpdata->drawing_area), cur);
        gdk_cursor_unref (cur);
        g_object_unref (gpdata->queuecursor_pixbuf);
        gpdata->queuecursor_pixbuf = NULL;
    }
    else
    {
        gdk_window_set_cursor (gtk_widget_get_window (gpdata->drawing_area), NULL);
    }
    UNLOCK_BUFFER (FALSE)

    return FALSE;
}

static void
remmina_plugin_vnc_queuecursor (RemminaProtocolWidget *gp, GdkPixbuf *pixbuf, gint x, gint y)
{
    RemminaPluginVncData *gpdata;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    if (gpdata->queuecursor_pixbuf)
    {
        g_object_unref (gpdata->queuecursor_pixbuf);
    }
    gpdata->queuecursor_pixbuf = pixbuf;
    gpdata->queuecursor_x = x;
    gpdata->queuecursor_y = y;
    if (!gpdata->queuecursor_handler)
    {
        gpdata->queuecursor_handler = IDLE_ADD ((GSourceFunc) remmina_plugin_vnc_setcursor, gp);
    }
}

typedef struct _RemminaKeyVal
{
    guint keyval;
    guint16 keycode;
} RemminaKeyVal;

/***************************** LibVNCClient related codes *********************************/
#include <rfb/rfbclient.h>

#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
static const uint32_t remmina_plugin_vnc_no_encrypt_auth_types[] = {rfbNoAuth, rfbVncAuth, rfbMSLogon, 0};
#endif

static void
remmina_plugin_vnc_process_vnc_event (RemminaProtocolWidget *gp)
{
    RemminaPluginVncEvent *event;
    RemminaPluginVncData *gpdata;
    rfbClient *cl;
    gchar buf[100];

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    cl = (rfbClient*) gpdata->client;
    while ((event = g_queue_pop_head (gpdata->vnc_event_queue)) != NULL)
    {
        if (cl)
        {
            switch (event->event_type)
            {
            case REMMINA_PLUGIN_VNC_EVENT_KEY:
                SendKeyEvent (cl, event->event_data.key.keyval, event->event_data.key.pressed);
                break;
            case REMMINA_PLUGIN_VNC_EVENT_POINTER:
                SendPointerEvent (cl, event->event_data.pointer.x, event->event_data.pointer.y,
                    event->event_data.pointer.button_mask);
                break;
            case REMMINA_PLUGIN_VNC_EVENT_CUTTEXT:
                SendClientCutText (cl, event->event_data.text.text, strlen (event->event_data.text.text));
                break;
            case REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN:
                TextChatOpen (cl);
                break;
            case REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND:
                TextChatSend (cl, event->event_data.text.text);
                break;
            case REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE:
                TextChatClose (cl);
                TextChatFinish (cl);
                break;
            }
        }
        remmina_plugin_vnc_event_free (event);
    }
    if (read (gpdata->vnc_event_pipe[0], buf, sizeof (buf)))
    {
        /* Ignore */
    }
}

typedef struct _RemminaPluginVncCuttextParam
{
    RemminaProtocolWidget *gp;
    gchar *text;
    gint textlen;
} RemminaPluginVncCuttextParam;

static void
remmina_plugin_vnc_update_quality (rfbClient *cl, gint quality)
{
    switch (quality)
    {
    case 9:
        cl->appData.useBGR233 = 0;
        cl->appData.encodingsString = "copyrect hextile raw";
        cl->appData.compressLevel = 0;
        cl->appData.qualityLevel = 9;
        break;
    case 2:
        cl->appData.useBGR233 = 0;
        cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
        cl->appData.compressLevel = 3;
        cl->appData.qualityLevel = 7;
        break;
    case 1:
        cl->appData.useBGR233 = 0;
        cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
        cl->appData.compressLevel = 5;
        cl->appData.qualityLevel = 5;
        break;
    case 0:
    default:
        cl->appData.useBGR233 = 1;
        cl->appData.encodingsString = "tight zrle ultra copyrect hextile zlib corre rre raw";
        cl->appData.compressLevel = 9;
        cl->appData.qualityLevel = 0;
        break;
    }
}

static void
remmina_plugin_vnc_update_colordepth (rfbClient *cl, gint colordepth)
{
    cl->format.depth = colordepth;
    cl->format.bigEndian = 0;
    cl->appData.requestedDepth = colordepth;

    switch (colordepth)
    {
    case 24:
        cl->format.bitsPerPixel = 32;
        cl->format.redMax = 0xff;
        cl->format.greenMax = 0xff;
        cl->format.blueMax = 0xff;
        cl->format.redShift = 16;
        cl->format.greenShift = 8;
        cl->format.blueShift = 0;
        break;
    case 16:
        cl->format.bitsPerPixel = 16;
        cl->format.redMax = 0x1f;
        cl->format.greenMax = 0x3f;
        cl->format.blueMax = 0x1f;
        cl->format.redShift = 11;
        cl->format.greenShift = 5;
        cl->format.blueShift = 0;
        break;
    case 15:
        cl->format.bitsPerPixel = 16;
        cl->format.redMax = 0x1f;
        cl->format.greenMax = 0x1f;
        cl->format.blueMax = 0x1f;
        cl->format.redShift = 10;
        cl->format.greenShift = 5;
        cl->format.blueShift = 0;
        break;
    case 8:
    default:
        cl->format.bitsPerPixel = 8;
        cl->format.redMax = 7;
        cl->format.greenMax = 7;
        cl->format.blueMax = 3;
        cl->format.redShift = 0;
        cl->format.greenShift = 3;
        cl->format.blueShift = 6;
        break;
    }
}

static rfbBool
remmina_plugin_vnc_rfb_allocfb (rfbClient *cl)
{
    RemminaProtocolWidget *gp;
    RemminaPluginVncData *gpdata;
    gint width, height, depth, size;
    gboolean scale;
    GdkPixbuf *new_pixbuf, *old_pixbuf;

    gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL));
    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    width = cl->width;
    height = cl->height;
    depth = cl->format.bitsPerPixel;
    size = width * height * (depth / 8);

    /* Putting gdk_pixbuf_new inside a gdk_thread_enter/leave pair could cause dead-lock! */
    new_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height);
    if (new_pixbuf == NULL) return FALSE;
    gdk_pixbuf_fill (new_pixbuf, 0);
    old_pixbuf = gpdata->rgb_buffer;

    LOCK_BUFFER (TRUE)

    remmina_plugin_service->protocol_plugin_set_width (gp, cl->width);
    remmina_plugin_service->protocol_plugin_set_height (gp, cl->height);

    gpdata->rgb_buffer = new_pixbuf;

    if (gpdata->vnc_buffer) g_free (gpdata->vnc_buffer);
    gpdata->vnc_buffer = (guchar*) g_malloc (size);
    cl->frameBuffer = gpdata->vnc_buffer;

    UNLOCK_BUFFER (TRUE)

    if (old_pixbuf) g_object_unref (old_pixbuf);
    
    scale = remmina_plugin_service->protocol_plugin_get_scale (gp);
    THREADS_ENTER
    remmina_plugin_vnc_update_scale (gp, scale);
    THREADS_LEAVE

    if (gpdata->scale_handler == 0) remmina_plugin_vnc_update_scale_buffer (gp, TRUE);

    /* Notify window of change so that scroll border can be hidden or shown if needed */
    remmina_plugin_service->protocol_plugin_emit_signal (gp, "desktop-resize");

    /* Refresh the client's updateRect - bug in xvncclient */
    cl->updateRect.w = width;
    cl->updateRect.h = height;

    return TRUE;
}

static gint
remmina_plugin_vnc_bits (gint n)
{
    gint b = 0;
    while (n) { b++; n >>= 1; }
    return b ? b : 1;
}

static gboolean
remmina_plugin_vnc_queue_draw_area_real (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    gint x, y, w, h;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (GTK_IS_WIDGET (gp) && gpdata->connected)
    {
        LOCK_BUFFER (FALSE)
        x = gpdata->queuedraw_x;
        y = gpdata->queuedraw_y;
        w = gpdata->queuedraw_w;
        h = gpdata->queuedraw_h;
        gpdata->queuedraw_handler = 0;
        UNLOCK_BUFFER (FALSE)

        gtk_widget_queue_draw_area (GTK_WIDGET (gp), x, y, w, h);
    }
    return FALSE;
}

static void
remmina_plugin_vnc_queue_draw_area (RemminaProtocolWidget *gp, gint x, gint y, gint w, gint h)
{
    RemminaPluginVncData *gpdata;
    gint nx2, ny2, ox2, oy2;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    LOCK_BUFFER (TRUE)
    if (gpdata->queuedraw_handler)
    {
        nx2 = x + w;
        ny2 = y + h;
        ox2 = gpdata->queuedraw_x + gpdata->queuedraw_w;
        oy2 = gpdata->queuedraw_y + gpdata->queuedraw_h;
        gpdata->queuedraw_x = MIN (gpdata->queuedraw_x, x);
        gpdata->queuedraw_y = MIN (gpdata->queuedraw_y, y);
        gpdata->queuedraw_w = MAX (ox2, nx2) - gpdata->queuedraw_x;
        gpdata->queuedraw_h = MAX (oy2, ny2) - gpdata->queuedraw_y;
    }
    else
    {
        gpdata->queuedraw_x = x;
        gpdata->queuedraw_y = y;
        gpdata->queuedraw_w = w;
        gpdata->queuedraw_h = h;
        gpdata->queuedraw_handler = IDLE_ADD ((GSourceFunc) remmina_plugin_vnc_queue_draw_area_real, gp);
    }
    UNLOCK_BUFFER (TRUE)
}

static void
remmina_plugin_vnc_rfb_fill_buffer (rfbClient* cl, guchar *dest, gint dest_rowstride,
    guchar *src, gint src_rowstride, guchar *mask, gint w, gint h)
{
    guchar *destptr, *srcptr;
    gint bytesPerPixel;
    guint32 pixel;
    gint ix, iy;
    gint i;
    guchar c;
    gint rs, gs, bs, rm, gm, bm, rl, gl, bl, rr, gr, br;
    gint r;

    bytesPerPixel = cl->format.bitsPerPixel / 8;
    switch (cl->format.bitsPerPixel)
    {
    case 32:
        /* The following codes fill in the Alpha channel swap red/green value */
        for (iy = 0; iy < h; iy++)
        {
            destptr = dest + iy * dest_rowstride;
            srcptr = src + iy * src_rowstride;
            for (ix = 0; ix < w; ix++)
            {
                *destptr++ = *(srcptr + 2);
                *destptr++ = *(srcptr + 1);
                *destptr++ = *srcptr;
                if (mask) *destptr++ = (*mask++) ? 0xff : 0x00;
                srcptr += 4;
            }
        }
        break;
    default:
        rm = cl->format.redMax;
        gm = cl->format.greenMax;
        bm = cl->format.blueMax;
        rr = remmina_plugin_vnc_bits (rm);
        gr = remmina_plugin_vnc_bits (gm);
        br = remmina_plugin_vnc_bits (bm);
        rl = 8 - rr;
        gl = 8 - gr;
        bl = 8 - br;
        rs = cl->format.redShift;
        gs = cl->format.greenShift;
        bs = cl->format.blueShift;
        for (iy = 0; iy < h; iy++)
        {
            destptr = dest + iy * dest_rowstride;
            srcptr = src + iy * src_rowstride;
            for (ix = 0; ix < w; ix++)
            {
                pixel = 0;
                for (i = 0; i < bytesPerPixel; i++) pixel += (*srcptr++) << (8 * i);
                c = (guchar) ((pixel >> rs) & rm) << rl;
                for (r = rr; r < 8; r *= 2) c |= c >> r;
                *destptr++ = c;
                c = (guchar) ((pixel >> gs) & gm) << gl;
                for (r = gr; r < 8; r *= 2) c |= c >> r;
                *destptr++ = c;
                c = (guchar) ((pixel >> bs) & bm) << bl;
                for (r = br; r < 8; r *= 2) c |= c >> r;
                *destptr++ = c;
                if (mask) *destptr++ = (*mask++) ? 0xff : 0x00;
            }
        }
        break;
    }
}

static void
remmina_plugin_vnc_rfb_updatefb (rfbClient* cl, int x, int y, int w, int h)
{
    RemminaProtocolWidget *gp;
    RemminaPluginVncData *gpdata;
    gint bytesPerPixel;
    gint rowstride;
    gint width;

    gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL));
    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    LOCK_BUFFER (TRUE)

    if (w >= 1 || h >= 1)
    {
        width = remmina_plugin_service->protocol_plugin_get_width (gp);
        bytesPerPixel = cl->format.bitsPerPixel / 8;
        rowstride = gdk_pixbuf_get_rowstride (gpdata->rgb_buffer);
        remmina_plugin_vnc_rfb_fill_buffer (cl,
            gdk_pixbuf_get_pixels (gpdata->rgb_buffer) + y * rowstride + x * 3, rowstride,
            gpdata->vnc_buffer + ((y * width + x) * bytesPerPixel), width * bytesPerPixel,
            NULL, w, h);
    }

    if (remmina_plugin_service->protocol_plugin_get_scale (gp))
    {
        remmina_plugin_vnc_scale_area (gp, &x, &y, &w, &h);
    }

    UNLOCK_BUFFER (TRUE)

    remmina_plugin_vnc_queue_draw_area (gp, x, y, w, h);
}

static gboolean
remmina_plugin_vnc_queue_cuttext (RemminaPluginVncCuttextParam *param)
{
    RemminaProtocolWidget *gp;
    RemminaPluginVncData *gpdata;
    GTimeVal t;
    glong diff;

    gp = param->gp;
    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (GTK_IS_WIDGET (gp) && gpdata->connected)
    {
        g_get_current_time (&t);
        diff = (t.tv_sec - gpdata->clipboard_timer.tv_sec) * 10 + (t.tv_usec - gpdata->clipboard_timer.tv_usec) / 100000;
        if (diff >= 10)
        {
            gpdata->clipboard_timer = t;
            gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), param->text, param->textlen);
        }
    }
    g_free (param->text);
    g_free (param);
    return FALSE;
}

static void
remmina_plugin_vnc_rfb_cuttext (rfbClient* cl, const char *text, int textlen)
{
    RemminaPluginVncCuttextParam *param;

    param = g_new (RemminaPluginVncCuttextParam, 1);
    param->gp = (RemminaProtocolWidget*) rfbClientGetClientData (cl, NULL);
    param->text = g_malloc (textlen);
    memcpy (param->text, text, textlen);
    param->textlen = textlen;
    IDLE_ADD ((GSourceFunc) remmina_plugin_vnc_queue_cuttext, param);
}

static char*
remmina_plugin_vnc_rfb_password (rfbClient *cl)
{
    RemminaProtocolWidget *gp;
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    gint ret;
    gchar *pwd = NULL;

    gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL));
    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    gpdata->auth_called = TRUE;
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);

    if (gpdata->auth_first)
    {
        THREADS_ENTER
        pwd = remmina_plugin_service->file_get_secret (remminafile, "password");
        THREADS_LEAVE
    }
    if (!pwd)
    {
        THREADS_ENTER
        ret = remmina_plugin_service->protocol_plugin_init_authpwd (gp, REMMINA_AUTHPWD_TYPE_PROTOCOL);
        THREADS_LEAVE

        if (ret == GTK_RESPONSE_OK)
        {
            pwd = remmina_plugin_service->protocol_plugin_init_get_password (gp);
        }
        else
        {
            gpdata->connected = FALSE;
        }
    }
    return pwd;
}

#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
static rfbCredential*
remmina_plugin_vnc_rfb_credential (rfbClient *cl, int credentialType)
{
    rfbCredential *cred;
    RemminaProtocolWidget *gp;
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    gint ret;
    gchar *s1, *s2;

    gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL));
    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    gpdata->auth_called = TRUE;
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);

    cred = g_new0 (rfbCredential, 1);

    switch (credentialType)
    {

    case rfbCredentialTypeUser:

        s1 = g_strdup (remmina_plugin_service->file_get_string (remminafile, "username"));
        THREADS_ENTER
        s2 = remmina_plugin_service->file_get_secret (remminafile, "password");
        THREADS_LEAVE
        if (gpdata->auth_first && s1 && s2)
        {
            cred->userCredential.username = s1;
            cred->userCredential.password = s2;
        }
        else
        {
            g_free (s1);
            g_free (s2);

            THREADS_ENTER
            ret = remmina_plugin_service->protocol_plugin_init_authuserpwd (gp, FALSE);
            THREADS_LEAVE

            if (ret == GTK_RESPONSE_OK)
            {
                cred->userCredential.username = remmina_plugin_service->protocol_plugin_init_get_username (gp);
                cred->userCredential.password = remmina_plugin_service->protocol_plugin_init_get_password (gp);
            }
            else
            {
                g_free (cred);
                cred = NULL;
                gpdata->connected = FALSE;
            }
        }
        break;

    case rfbCredentialTypeX509:
        if (gpdata->auth_first &&
            remmina_plugin_service->file_get_string (remminafile, "cacert"))
        {
            cred->x509Credential.x509CACertFile = g_strdup (remmina_plugin_service->file_get_string (remminafile, "cacert"));
            cred->x509Credential.x509CACrlFile = g_strdup (remmina_plugin_service->file_get_string (remminafile, "cacrl"));
            cred->x509Credential.x509ClientCertFile = g_strdup (remmina_plugin_service->file_get_string (remminafile, "clientcert"));
            cred->x509Credential.x509ClientKeyFile = g_strdup (remmina_plugin_service->file_get_string (remminafile, "clientkey"));
        }
        else
        {
            THREADS_ENTER
            ret = remmina_plugin_service->protocol_plugin_init_authx509 (gp);
            THREADS_LEAVE

            if (ret == GTK_RESPONSE_OK)
            {
                cred->x509Credential.x509CACertFile = remmina_plugin_service->protocol_plugin_init_get_cacert (gp);
                cred->x509Credential.x509CACrlFile = remmina_plugin_service->protocol_plugin_init_get_cacrl (gp);
                cred->x509Credential.x509ClientCertFile = remmina_plugin_service->protocol_plugin_init_get_clientcert (gp);
                cred->x509Credential.x509ClientKeyFile = remmina_plugin_service->protocol_plugin_init_get_clientkey (gp);
            }
            else
            {
                g_free (cred);
                cred = NULL;
                gpdata->connected = FALSE;
            }
        }
        break;

    default:
        g_free (cred);
        cred = NULL;
        break;
    }
    return cred;
}
#endif

static void 
remmina_plugin_vnc_rfb_cursor_shape (rfbClient *cl, int xhot, int yhot, int width, int height, int bytesPerPixel)
{
    RemminaProtocolWidget *gp;
    RemminaPluginVncData *gpdata;
    guchar *pixbuf_data;
    GdkPixbuf *pixbuf;

    gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL));
    if (!gtk_widget_get_window (GTK_WIDGET (gp))) return;
    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    if (width && height)
    {
        pixbuf_data = g_malloc (width * height * 4);
        remmina_plugin_vnc_rfb_fill_buffer (cl,
            pixbuf_data, width * 4,
            cl->rcSource, width * cl->format.bitsPerPixel / 8,
            cl->rcMask, width, height);
        pixbuf = gdk_pixbuf_new_from_data (pixbuf_data, GDK_COLORSPACE_RGB,
            TRUE, 8, width, height,
            width * 4, (GdkPixbufDestroyNotify) g_free, NULL);

        LOCK_BUFFER (TRUE)
        remmina_plugin_vnc_queuecursor (gp, pixbuf, xhot, yhot);
        UNLOCK_BUFFER (TRUE)
    }
}

static void
remmina_plugin_vnc_rfb_bell (rfbClient *cl)
{
    RemminaProtocolWidget *gp;
    GdkWindow *window;
    
    gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL));
    window = gtk_widget_get_window (GTK_WIDGET (gp));

    if (window) gdk_window_beep (window);
}

/* Translate known VNC messages. It's for intltool only, not for gcc */
#ifdef __DO_NOT_COMPILE_ME__
N_("Unable to connect to VNC server")
N_("Couldn't convert '%s' to host address")
N_("VNC connection failed: %s")
N_("Your connection has been rejected.")
#endif
/* TODO: We only store the last message at this moment. */
#define MAX_ERROR_LENGTH 1000
static gchar vnc_error[MAX_ERROR_LENGTH + 1];
static void
remmina_plugin_vnc_rfb_output(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    gchar *f, *p;

    /* eliminate the last \n */
    f = g_strdup (format);
    if (f[strlen (f) - 1] == '\n') f[strlen (f) - 1] = '\0';

    if (g_strcmp0 (f, "VNC connection failed: %s") == 0)
    {
        p = va_arg (args, gchar*);
        g_snprintf (vnc_error, MAX_ERROR_LENGTH, _(f), _(p));
    }
    else
    {
        g_vsnprintf (vnc_error, MAX_ERROR_LENGTH, _(f), args);
    }
    g_free (f);
    va_end(args);

    remmina_plugin_service->log_printf ("[VNC]%s\n", vnc_error);
}

static void
remmina_plugin_vnc_chat_on_send (RemminaProtocolWidget *gp, const gchar *text)
{
    gchar *ptr;

    /* Need to add a line-feed for UltraVNC */
    ptr = g_strdup_printf ("%s\n", text);
    remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_SEND, ptr, NULL, NULL);
    g_free (ptr);
}

static void
remmina_plugin_vnc_chat_on_destroy (RemminaProtocolWidget *gp)
{
    remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_CLOSE, NULL, NULL, NULL);
}

static gboolean
remmina_plugin_vnc_close_chat (RemminaProtocolWidget *gp)
{
    remmina_plugin_service->protocol_plugin_chat_close (gp);
    return FALSE;
}

static gboolean
remmina_plugin_vnc_open_chat (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    rfbClient *cl;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    cl = (rfbClient*) gpdata->client;

    remmina_plugin_service->protocol_plugin_chat_open (gp, cl->desktopName,
        remmina_plugin_vnc_chat_on_send, remmina_plugin_vnc_chat_on_destroy);
    remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_CHAT_OPEN, NULL, NULL, NULL);
    return FALSE;
}

static void
remmina_plugin_vnc_rfb_chat (rfbClient* cl, int value, char *text)
{
    RemminaProtocolWidget *gp;

    gp = (RemminaProtocolWidget*) (rfbClientGetClientData (cl, NULL));
    switch (value)
    {
    case rfbTextChatOpen:
        IDLE_ADD ((GSourceFunc) remmina_plugin_vnc_open_chat, gp);
        break;
    case rfbTextChatClose:
        /* Do nothing... but wait for the next rfbTextChatFinished signal */
        break;
    case rfbTextChatFinished:
        IDLE_ADD ((GSourceFunc) remmina_plugin_vnc_close_chat, gp);
        break;
    default:
        /* value is the text length */
        THREADS_ENTER
        remmina_plugin_service->protocol_plugin_chat_receive (gp, text);
        THREADS_LEAVE
        break;
    }
}

static gboolean
remmina_plugin_vnc_incoming_connection (RemminaProtocolWidget *gp, rfbClient *cl)
{
    RemminaPluginVncData *gpdata;
    fd_set fds;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    gpdata->listen_sock = ListenAtTcpPort (cl->listenPort);
    if (gpdata->listen_sock < 0) return FALSE;

    remmina_plugin_service->protocol_plugin_init_show_listen (gp, cl->listenPort);

    remmina_plugin_service->protocol_plugin_start_reverse_tunnel (gp, cl->listenPort);

    FD_ZERO (&fds); 
    FD_SET (gpdata->listen_sock, &fds);
    select (gpdata->listen_sock + 1, &fds, NULL, NULL, NULL);

    if (!FD_ISSET (gpdata->listen_sock, &fds))
    {
        close (gpdata->listen_sock);
        gpdata->listen_sock = -1;
        return FALSE;
    }

    cl->sock = AcceptTcpConnection (gpdata->listen_sock);
    close (gpdata->listen_sock);
    gpdata->listen_sock = -1;
    if (cl->sock < 0 || !SetNonBlocking (cl->sock))
    {
        return FALSE;
    }

    return TRUE;
}

static gboolean
remmina_plugin_vnc_main_loop (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    gint ret;
    rfbClient *cl;
    fd_set fds;
    struct timeval timeout;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    if (!gpdata->connected)
    {
        gpdata->running = FALSE;
        return FALSE;
    }

    cl = (rfbClient*) gpdata->client;

    timeout.tv_sec = 10;
    timeout.tv_usec = 0;
    FD_ZERO (&fds);
    FD_SET (cl->sock, &fds);
    FD_SET (gpdata->vnc_event_pipe[0], &fds);
    ret = select (MAX (cl->sock, gpdata->vnc_event_pipe[0]) + 1, &fds, NULL, NULL, &timeout);

    /* Sometimes it returns <0 when opening a modal dialog in other window. Absolutely weird */
    /* So we continue looping anyway */
    if (ret <= 0) return TRUE;

    if (FD_ISSET (gpdata->vnc_event_pipe[0], &fds))
    {
        remmina_plugin_vnc_process_vnc_event (gp);
    }
    if (FD_ISSET (cl->sock, &fds))
    {
        ret = HandleRFBServerMessage (cl);
        if (!ret)
        {
            gpdata->running = FALSE;
            if (gpdata->connected && !remmina_plugin_service->protocol_plugin_is_closed (gp))
            {
                IDLE_ADD ((GSourceFunc) remmina_plugin_service->protocol_plugin_close_connection, gp);
            }
            return FALSE;
        }
    }

    return TRUE;
}

static gboolean
remmina_plugin_vnc_main (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    rfbClient *cl = NULL;
    gchar *host;
    gchar *s = NULL;

    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);
    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    gpdata->running = TRUE;

    rfbClientLog = remmina_plugin_vnc_rfb_output;
    rfbClientErr = remmina_plugin_vnc_rfb_output;

    while (gpdata->connected)
    {
        gpdata->auth_called = FALSE;

        host = remmina_plugin_service->protocol_plugin_start_direct_tunnel (gp, 5900, TRUE);

        if (host == NULL)
        {
            gpdata->connected = FALSE;
            break;
        }

        cl = rfbGetClient(8, 3, 4);
        cl->MallocFrameBuffer = remmina_plugin_vnc_rfb_allocfb;
        cl->canHandleNewFBSize = TRUE;
        cl->GetPassword = remmina_plugin_vnc_rfb_password;
#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
        cl->GetCredential = remmina_plugin_vnc_rfb_credential;
#endif
        cl->GotFrameBufferUpdate = remmina_plugin_vnc_rfb_updatefb;
        cl->GotXCutText = (remmina_plugin_service->file_get_int (remminafile, "disableclipboard", FALSE) ?
            NULL : remmina_plugin_vnc_rfb_cuttext);
        cl->GotCursorShape = remmina_plugin_vnc_rfb_cursor_shape;
        cl->Bell = remmina_plugin_vnc_rfb_bell;
        cl->HandleTextChat = remmina_plugin_vnc_rfb_chat;
        rfbClientSetClientData (cl, NULL, gp);

        if (host[0] == '\0')
        {
            cl->serverHost = strdup (host);
            cl->listenSpecified = TRUE;
            if (remmina_plugin_service->file_get_int (remminafile, "ssh_enabled", FALSE))
            {
                /* When we use reverse tunnel, the local port does not really matter.
                 * Hardcode a default port just in case the remote port is customized
                 * to a privilege port then we will have problem listening. */
                cl->listenPort = 5500;
            }
            else
            {
                cl->listenPort = remmina_plugin_service->file_get_int (remminafile, "listenport", 5500);
            }

            remmina_plugin_vnc_incoming_connection (gp, cl);
        }
        else
        {
            remmina_plugin_service->get_server_port (host, 5900, &s, &cl->serverPort);
            cl->serverHost = strdup (s);
            g_free (s);

            /* Support short-form (:0, :1) */
            if (cl->serverPort < 100) cl->serverPort += 5900;
        }
        g_free (host);
        host = NULL;

        if (remmina_plugin_service->file_get_string (remminafile, "proxy"))
        {
#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
            cl->destHost = cl->serverHost;
            cl->destPort = cl->serverPort;
            remmina_plugin_service->get_server_port (remmina_plugin_service->file_get_string (remminafile, "proxy"), 5900,
                &s, &cl->serverPort);
            cl->serverHost = strdup (s);
            g_free (s);
#endif
        }

        cl->appData.useRemoteCursor = (remmina_plugin_service->file_get_int (remminafile, "showcursor", FALSE) ? FALSE : TRUE);

        remmina_plugin_vnc_update_quality (cl, remmina_plugin_service->file_get_int (remminafile, "quality", 0));
        remmina_plugin_vnc_update_colordepth (cl, remmina_plugin_service->file_get_int (remminafile, "colordepth", 8));
        SetFormatAndEncodings (cl);

        if (remmina_plugin_service->file_get_int (remminafile, "disableencryption", FALSE))
        {
#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
            SetClientAuthSchemes (cl, remmina_plugin_vnc_no_encrypt_auth_types, -1);
#endif
        }

        if (rfbInitClient (cl, NULL, NULL)) break;

        /* If the authentication is not called, it has to be a fatel error and must quit */
        if (!gpdata->auth_called)
        {
            gpdata->connected = FALSE;
            break;
        }

        /* vnc4server reports "already in use" after authentication. Workaround here */
        if (strstr(vnc_error, "The server is already in use"))
        {
            gpdata->connected = FALSE;
            gpdata->auth_called = FALSE;
            break;
        }

        /* Otherwise, it's a password error. Try to clear saved password if any */
        remmina_plugin_service->file_set_string (remminafile, "password", NULL);

        if (!gpdata->connected) break;

        THREADS_ENTER
        remmina_plugin_service->protocol_plugin_init_show_retry (gp);
        THREADS_LEAVE
        /* It's safer to sleep a while before reconnect */
        sleep (2);

        gpdata->auth_first = FALSE;
    }

    if (!gpdata->connected)
    {
        if (cl && !gpdata->auth_called && !(remmina_plugin_service->protocol_plugin_has_error (gp)))
        {
            remmina_plugin_service->protocol_plugin_set_error (gp, "%s", vnc_error);
        }
        gpdata->running = FALSE;

        IDLE_ADD ((GSourceFunc) remmina_plugin_service->protocol_plugin_close_connection, gp);

        return FALSE;
    }

    THREADS_ENTER
    remmina_plugin_service->protocol_plugin_init_save_cred (gp);
    THREADS_LEAVE

    gpdata->client = cl;

    remmina_plugin_service->protocol_plugin_emit_signal (gp, "connect");

    if (remmina_plugin_service->file_get_int (remminafile, "disableserverinput", FALSE))
    {
        PermitServerInput(cl, 1);
    }

    if (gpdata->thread)
    {
        while (remmina_plugin_vnc_main_loop (gp)) { }
        gpdata->running = FALSE;
    }
    else
    {
        IDLE_ADD ((GSourceFunc) remmina_plugin_vnc_main_loop, gp);
    }

    return FALSE;
}

#ifdef HAVE_PTHREAD
static gpointer
remmina_plugin_vnc_main_thread (gpointer data)
{
    pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL);

    CANCEL_ASYNC
    remmina_plugin_vnc_main ((RemminaProtocolWidget*) data);
    return NULL;
}
#endif

static gboolean
remmina_plugin_vnc_on_motion (GtkWidget *widget, GdkEventMotion *event, RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    gint x, y;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (!gpdata->connected || !gpdata->client) return FALSE;
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);
    if (remmina_plugin_service->file_get_int (remminafile, "viewonly", FALSE)) return FALSE;

    if (remmina_plugin_service->protocol_plugin_get_scale (gp))
    {
        x = event->x * remmina_plugin_service->protocol_plugin_get_width (gp) / gpdata->scale_width;
        y = event->y * remmina_plugin_service->protocol_plugin_get_height (gp) / gpdata->scale_height;
    }
    else
    {
        x = event->x;
        y = event->y;
    }
    remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_POINTER,
        GINT_TO_POINTER (x), GINT_TO_POINTER (y), GINT_TO_POINTER (gpdata->button_mask));
    return TRUE;
}

static gboolean
remmina_plugin_vnc_on_button (GtkWidget *widget, GdkEventButton *event, RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    gint x, y;
    gint mask;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (!gpdata->connected || !gpdata->client) return FALSE;
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);
    if (remmina_plugin_service->file_get_int (remminafile, "viewonly", FALSE)) return FALSE;

    /* We only accept 3 buttons */
    if (event->button < 1 || event->button > 3) return FALSE;
    /* We bypass 2button-press and 3button-press events */
    if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) return TRUE;

    mask = (1 << (event->button - 1));
    gpdata->button_mask = (event->type == GDK_BUTTON_PRESS ?
        (gpdata->button_mask | mask) :
        (gpdata->button_mask & (0xff - mask)));
    if (remmina_plugin_service->protocol_plugin_get_scale (gp))
    {
        x = event->x * remmina_plugin_service->protocol_plugin_get_width (gp) / gpdata->scale_width;
        y = event->y * remmina_plugin_service->protocol_plugin_get_height (gp) / gpdata->scale_height;
    }
    else
    {
        x = event->x;
        y = event->y;
    }
    remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_POINTER,
        GINT_TO_POINTER (x), GINT_TO_POINTER (y), GINT_TO_POINTER (gpdata->button_mask));
    return TRUE;
}

static gboolean
remmina_plugin_vnc_on_scroll (GtkWidget *widget, GdkEventScroll *event, RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    gint x, y;
    gint mask;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (!gpdata->connected || !gpdata->client) return FALSE;
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);
    if (remmina_plugin_service->file_get_int (remminafile, "viewonly", FALSE)) return FALSE;

    switch (event->direction)
    {
    case GDK_SCROLL_UP:
        mask = (1 << 3);
        break;
    case GDK_SCROLL_DOWN:
        mask = (1 << 4);
        break;
    case GDK_SCROLL_LEFT:
        mask = (1 << 5);
        break;
    case GDK_SCROLL_RIGHT:
        mask = (1 << 6);
        break;
    default:
        return FALSE;
    }

    if (remmina_plugin_service->protocol_plugin_get_scale (gp))
    {
        x = event->x * remmina_plugin_service->protocol_plugin_get_width (gp) / gpdata->scale_width;
        y = event->y * remmina_plugin_service->protocol_plugin_get_height (gp) / gpdata->scale_height;
    }
    else
    {
        x = event->x;
        y = event->y;
    }
    remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_POINTER,
        GINT_TO_POINTER (x), GINT_TO_POINTER (y), GINT_TO_POINTER (mask | gpdata->button_mask));
    remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_POINTER,
        GINT_TO_POINTER (x), GINT_TO_POINTER (y), GINT_TO_POINTER (gpdata->button_mask));

    return TRUE;
}

static void
remmina_plugin_vnc_release_key (RemminaProtocolWidget *gp, guint16 keycode)
{
    RemminaPluginVncData *gpdata;
    RemminaKeyVal *k;
    gint i;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (keycode == 0)
    {
        /* Send all release key events for previously pressed keys */
        for (i = 0; i < gpdata->pressed_keys->len; i++)
        {
            k = g_ptr_array_index (gpdata->pressed_keys, i);
            remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_KEY, GUINT_TO_POINTER (k->keyval), GINT_TO_POINTER (FALSE), NULL);
            g_free (k);
        }
        g_ptr_array_set_size (gpdata->pressed_keys, 0);
    }
    else
    {
        /* Unregister the keycode only */
        for (i = 0; i < gpdata->pressed_keys->len; i++)
        {
            k = g_ptr_array_index (gpdata->pressed_keys, i);
            if (k->keycode == keycode)
            {
                g_free (k);
                g_ptr_array_remove_index_fast (gpdata->pressed_keys, i);
                break;
            }
        }
    }
}

static gboolean
remmina_plugin_vnc_on_key (GtkWidget *widget, GdkEventKey *event, RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;
    RemminaKeyVal *k;
    guint keyval;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (!gpdata->connected || !gpdata->client) return FALSE;
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);
    if (remmina_plugin_service->file_get_int (remminafile, "viewonly", FALSE)) return FALSE;

    keyval = remmina_plugin_service->pref_keymap_get_keyval (
        remmina_plugin_service->file_get_string (remminafile, "gkeymap"), event->keyval);

    remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_KEY, GUINT_TO_POINTER (keyval),
        GINT_TO_POINTER (event->type == GDK_KEY_PRESS ? TRUE : FALSE), NULL);

    /* Register/unregister the pressed key */
    if (event->type == GDK_KEY_PRESS)
    {
        k = g_new (RemminaKeyVal, 1);
        k->keyval = keyval;
        k->keycode = event->hardware_keycode;
        g_ptr_array_add (gpdata->pressed_keys, k);
    }
    else
    {
        remmina_plugin_vnc_release_key (gp, event->hardware_keycode);
    }
    return TRUE;
}

static void
remmina_plugin_vnc_on_cuttext_request (GtkClipboard *clipboard, const gchar *text, RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    GTimeVal t;
    glong diff;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    if (text)
    {
        /* A timer (1 second) to avoid clipboard "loopback": text cut out from VNC won't paste back into VNC */
        g_get_current_time (&t);
        diff = (t.tv_sec - gpdata->clipboard_timer.tv_sec) * 10 + (t.tv_usec - gpdata->clipboard_timer.tv_usec) / 100000;
        if (diff < 10) return;

        gpdata->clipboard_timer = t;
        remmina_plugin_vnc_event_push (gp, REMMINA_PLUGIN_VNC_EVENT_CUTTEXT, (gpointer) text, NULL, NULL);
    }
}

static void
remmina_plugin_vnc_on_cuttext (GtkClipboard *clipboard, GdkEvent *event, RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    if (!gpdata->connected || !gpdata->client) return;
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);
    if (remmina_plugin_service->file_get_int (remminafile, "viewonly", FALSE)) return;

    gtk_clipboard_request_text (clipboard, (GtkClipboardTextReceivedFunc) remmina_plugin_vnc_on_cuttext_request, gp);
}

static void
remmina_plugin_vnc_on_realize (RemminaProtocolWidget *gp, gpointer data)
{
    RemminaFile *remminafile;
    GdkCursor *cursor;
    GdkPixbuf *pixbuf;

    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);

    if (remmina_plugin_service->file_get_int (remminafile, "showcursor", FALSE))
    {
        /* Hide local cursor (show a small dot instead) */
        pixbuf = gdk_pixbuf_new_from_xpm_data (dot_cursor_xpm);
        cursor = gdk_cursor_new_from_pixbuf (gdk_display_get_default (), pixbuf, dot_cursor_x_hot, dot_cursor_y_hot);
        gdk_pixbuf_unref (pixbuf);
        gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (gp)), cursor);
        gdk_cursor_unref (cursor);
    }
}

/******************************************************************************************/

static gboolean
remmina_plugin_vnc_open_connection (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);

    gpdata->connected = TRUE;

    remmina_plugin_service->protocol_plugin_register_hostkey (gp, gpdata->drawing_area);

    g_signal_connect (G_OBJECT (gp), "realize",
        G_CALLBACK (remmina_plugin_vnc_on_realize), NULL);
    g_signal_connect (G_OBJECT (gpdata->drawing_area), "motion-notify-event",
        G_CALLBACK (remmina_plugin_vnc_on_motion), gp);
    g_signal_connect (G_OBJECT (gpdata->drawing_area), "button-press-event",
        G_CALLBACK (remmina_plugin_vnc_on_button), gp);
    g_signal_connect (G_OBJECT (gpdata->drawing_area), "button-release-event",
        G_CALLBACK (remmina_plugin_vnc_on_button), gp);
    g_signal_connect (G_OBJECT (gpdata->drawing_area), "scroll-event",
        G_CALLBACK (remmina_plugin_vnc_on_scroll), gp);
    g_signal_connect (G_OBJECT (gpdata->drawing_area), "key-press-event",
        G_CALLBACK (remmina_plugin_vnc_on_key), gp);
    g_signal_connect (G_OBJECT (gpdata->drawing_area), "key-release-event",
        G_CALLBACK (remmina_plugin_vnc_on_key), gp);

    if (!remmina_plugin_service->file_get_int (remminafile, "disableclipboard", FALSE))
    {
        gpdata->clipboard_handler = g_signal_connect (G_OBJECT (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)),
            "owner-change", G_CALLBACK (remmina_plugin_vnc_on_cuttext), gp);
    }

#ifdef HAVE_PTHREAD
    if (pthread_create (&gpdata->thread, NULL, remmina_plugin_vnc_main_thread, gp))
    {
        /* I don't think this will ever happen... */
        g_print ("Failed to initialize pthread. Falling back to non-thread mode...\n");
        g_timeout_add (0, (GSourceFunc) remmina_plugin_vnc_main, gp);
        gpdata->thread = 0;
    }
#else
    g_timeout_add (0, (GSourceFunc) remmina_plugin_vnc_main, gp);
#endif

    return TRUE;
}

static gboolean
remmina_plugin_vnc_close_connection_timeout (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    /* wait until the running attribute is set to false by the VNC thread */
    if (gpdata->running) return TRUE;

    /* unregister the clipboard monitor */
    if (gpdata->clipboard_handler)
    {
        g_signal_handler_disconnect (G_OBJECT (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)),
            gpdata->clipboard_handler);
        gpdata->clipboard_handler = 0;
    }

    if (gpdata->queuecursor_handler)
    {
        g_source_remove (gpdata->queuecursor_handler);
        gpdata->queuecursor_handler = 0;
    }
    if (gpdata->queuecursor_pixbuf)
    {
        g_object_unref (gpdata->queuecursor_pixbuf);
        gpdata->queuecursor_pixbuf = NULL;
    }

    if (gpdata->queuedraw_handler)
    {
        g_source_remove (gpdata->queuedraw_handler);
        gpdata->queuedraw_handler = 0;
    }
    if (gpdata->scale_handler)
    {
        g_source_remove (gpdata->scale_handler);
        gpdata->scale_handler = 0;
    }
    if (gpdata->listen_sock >= 0)
    {
        close (gpdata->listen_sock);
    }
    if (gpdata->client)
    {
        rfbClientCleanup((rfbClient*) gpdata->client);
        gpdata->client = NULL;
    }
    if (gpdata->rgb_buffer)
    {
        g_object_unref (gpdata->rgb_buffer);
        gpdata->rgb_buffer = NULL;
    }
    if (gpdata->vnc_buffer)
    {
        g_free (gpdata->vnc_buffer);
        gpdata->vnc_buffer = NULL;
    }
    if (gpdata->scale_buffer)
    {
        g_object_unref (gpdata->scale_buffer);
        gpdata->scale_buffer = NULL;
    }
    g_ptr_array_free (gpdata->pressed_keys, TRUE);
    remmina_plugin_vnc_event_free_all (gp);
    g_queue_free (gpdata->vnc_event_queue);
    close (gpdata->vnc_event_pipe[0]);
    close (gpdata->vnc_event_pipe[1]);
    
#ifdef HAVE_PTHREAD
    pthread_mutex_destroy (&gpdata->buffer_mutex);
#endif

    remmina_plugin_service->protocol_plugin_emit_signal (gp, "disconnect");

    return FALSE;
}

static gboolean
remmina_plugin_vnc_close_connection (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    gpdata->connected = FALSE;

#ifdef HAVE_PTHREAD
    if (gpdata->thread)
    {
        pthread_cancel (gpdata->thread);
        if (gpdata->thread) pthread_join (gpdata->thread, NULL);
        gpdata->running = FALSE;
        remmina_plugin_vnc_close_connection_timeout (gp);
    }
    else
    {
        g_timeout_add (200, (GSourceFunc) remmina_plugin_vnc_close_connection_timeout, gp);
    }
#else
    g_timeout_add (200, (GSourceFunc) remmina_plugin_vnc_close_connection_timeout, gp);
#endif

    return FALSE;
}

static gboolean
remmina_plugin_vnc_query_feature (RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
{
    RemminaPluginVncData *gpdata;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    switch (feature->id)
    {
        case REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT:
            return (SupportsClient2Server ((rfbClient*) (gpdata->client), rfbSetServerInput) ?
                TRUE : FALSE);
        case REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT:
            return (SupportsClient2Server ((rfbClient*) (gpdata->client), rfbTextChat) ?
                TRUE : FALSE);
        default:
            return TRUE;
    }
}

static void
remmina_plugin_vnc_call_feature (RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
{
    RemminaPluginVncData *gpdata;
    RemminaFile *remminafile;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    remminafile = remmina_plugin_service->protocol_plugin_get_file (gp);
    switch (feature->id)
    {
        case REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY:
            remmina_plugin_vnc_update_quality ((rfbClient*) (gpdata->client),
                remmina_plugin_service->file_get_int (remminafile, "quality", 0));
            SetFormatAndEncodings ((rfbClient*) (gpdata->client));
            break;
        case REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY:
            break;
        case REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT:
            PermitServerInput ((rfbClient*) (gpdata->client),
                remmina_plugin_service->file_get_int (remminafile, "disableserverinput", FALSE) ? 1 : 0);
            break;
        case REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS:
            remmina_plugin_vnc_release_key (gp, 0);
            break;
        case REMMINA_PLUGIN_VNC_FEATURE_SCALE:
            remmina_plugin_vnc_update_scale (gp,
                remmina_plugin_service->file_get_int (remminafile, "scale", FALSE));
            break;
        case REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH:
            SendFramebufferUpdateRequest ((rfbClient*) (gpdata->client), 0, 0,
                remmina_plugin_service->protocol_plugin_get_width (gp),
                remmina_plugin_service->protocol_plugin_get_height (gp),
                FALSE);
            break;
        case REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT:
            remmina_plugin_vnc_open_chat (gp);
            break;
        default:
            break;
    }
}

static gboolean
remmina_plugin_vnc_on_draw (GtkWidget *widget, cairo_t *context, RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    GdkPixbuf *buffer;
    gboolean scale;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");

    LOCK_BUFFER (FALSE)

    scale = remmina_plugin_service->protocol_plugin_get_scale (gp);
    /* widget == gpdata->drawing_area */
    buffer = (scale ? gpdata->scale_buffer : gpdata->rgb_buffer);
    if (!buffer)
    {
        UNLOCK_BUFFER (FALSE)
        return FALSE;
    }

    cairo_rectangle (context, 0, 0, gtk_widget_get_allocated_width (widget),
        gtk_widget_get_allocated_height (widget));
    gdk_cairo_set_source_pixbuf (context, buffer, 0, 0);
    cairo_fill (context);

    UNLOCK_BUFFER (FALSE)
    return TRUE;
}

static gboolean
remmina_plugin_vnc_on_configure (GtkWidget *widget, GdkEventConfigure *event, RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;

    gpdata = (RemminaPluginVncData*) g_object_get_data (G_OBJECT (gp), "plugin-data");
    /* We do a delayed reallocating to improve performance */
    if (gpdata->scale_handler) g_source_remove (gpdata->scale_handler);
    gpdata->scale_handler = g_timeout_add (1000, (GSourceFunc) remmina_plugin_vnc_update_scale_buffer_main, gp);
    return FALSE;
}

static void
remmina_plugin_vnc_init (RemminaProtocolWidget *gp)
{
    RemminaPluginVncData *gpdata;
    gint flags;

    gpdata = g_new0 (RemminaPluginVncData, 1);
    g_object_set_data_full (G_OBJECT (gp), "plugin-data", gpdata, g_free);

    gpdata->drawing_area = gtk_drawing_area_new ();
    gtk_widget_show (gpdata->drawing_area);
    gtk_container_add (GTK_CONTAINER (gp), gpdata->drawing_area);

    gtk_widget_add_events (gpdata->drawing_area, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK
        | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
    gtk_widget_set_can_focus (gpdata->drawing_area, TRUE);

    g_signal_connect (G_OBJECT (gpdata->drawing_area), "draw",
        G_CALLBACK (remmina_plugin_vnc_on_draw), gp);
    g_signal_connect (G_OBJECT (gpdata->drawing_area), "configure_event",
        G_CALLBACK (remmina_plugin_vnc_on_configure), gp);

    gpdata->auth_first = TRUE;
    g_get_current_time (&gpdata->clipboard_timer);
    gpdata->listen_sock = -1;
    gpdata->pressed_keys = g_ptr_array_new ();
    gpdata->vnc_event_queue = g_queue_new ();
    if (pipe (gpdata->vnc_event_pipe))
    {
        g_print ("Error creating pipes.\n");
        gpdata->vnc_event_pipe[0] = 0;
        gpdata->vnc_event_pipe[1] = 0;
    }
    flags = fcntl (gpdata->vnc_event_pipe[0], F_GETFL, 0);
    fcntl (gpdata->vnc_event_pipe[0], F_SETFL, flags | O_NONBLOCK);
#ifdef HAVE_PTHREAD
    pthread_mutex_init (&gpdata->buffer_mutex, NULL);
#endif
}

static gpointer colordepth_list[] =
{
    "8", N_("256 colors"),
    "15", N_("High color (15 bit)"),
    "16", N_("High color (16 bit)"),
    "24", N_("True color (24 bit)"),
    NULL
};

static gpointer quality_list[] =
{
    "0", N_("Poor (fastest)"),
    "1", N_("Medium"),
    "2", N_("Good"),
    "9", N_("Best (slowest)"),
    NULL
};

static const RemminaProtocolSetting remmina_plugin_vnc_basic_settings[] =
{
    { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, NULL, NULL, FALSE, "_rfb._tcp", NULL },
#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
    { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "proxy", N_("Repeater"), FALSE, NULL, NULL },
#endif
    { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("User name"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, NULL, NULL, FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Show remote cursor"), TRUE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "viewonly", N_("View only"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), TRUE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", N_("Disable encryption"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableserverinput", N_("Disable server input"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
};

static const RemminaProtocolSetting remmina_plugin_vnci_basic_settings[] =
{
    { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "listenport", N_("Listen on port"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("User name"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, NULL, NULL, FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Color depth"), FALSE, colordepth_list, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Show remote cursor"), TRUE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "viewonly", N_("View only"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), TRUE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", N_("Disable encryption"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableserverinput", N_("Disable server input"), FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
};

static const RemminaProtocolSetting remmina_plugin_vnc_advanced_settings[] =
{
    { REMMINA_PROTOCOL_SETTING_TYPE_SCALE, NULL, NULL, FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP, NULL, NULL, FALSE, NULL, NULL },
    { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
};

static const RemminaProtocolFeature remmina_plugin_vnc_features[] =
{
    { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_QUALITY,
      GINT_TO_POINTER (REMMINA_PROTOCOL_FEATURE_PREF_RADIO), "quality", quality_list },
    { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_VIEWONLY,
      GINT_TO_POINTER (REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "viewonly", N_("View only") },
    { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_PLUGIN_VNC_FEATURE_PREF_DISABLESERVERINPUT,
      GINT_TO_POINTER (REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "disableserverinput", N_("Disable server input") },
    { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_REFRESH, N_("Refresh"), GTK_STOCK_REFRESH, NULL },
    { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_VNC_FEATURE_TOOL_CHAT, N_("Open Chat..."), "face-smile", NULL },
    { REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, REMMINA_PLUGIN_VNC_FEATURE_SCALE, NULL, NULL, NULL },
    { REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS, REMMINA_PLUGIN_VNC_FEATURE_UNFOCUS, NULL, NULL, NULL },
    { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
};

static RemminaProtocolPlugin remmina_plugin_vnc =
{
    REMMINA_PLUGIN_TYPE_PROTOCOL,
    "VNC",
    N_("VNC - Virtual Network Computing"),
    GETTEXT_PACKAGE,
    VERSION,

    "remmina-vnc",
    "remmina-vnc-ssh",
    remmina_plugin_vnc_basic_settings,
    remmina_plugin_vnc_advanced_settings,
    REMMINA_PROTOCOL_SSH_SETTING_TUNNEL,
    remmina_plugin_vnc_features,

    remmina_plugin_vnc_init,
    remmina_plugin_vnc_open_connection,
    remmina_plugin_vnc_close_connection,
    remmina_plugin_vnc_query_feature,
    remmina_plugin_vnc_call_feature
};

static RemminaProtocolPlugin remmina_plugin_vnci =
{
    REMMINA_PLUGIN_TYPE_PROTOCOL,
    "VNCI",
    N_("VNC - Incoming Connection"),
    GETTEXT_PACKAGE,
    VERSION,

    "remmina-vnc",
    "remmina-vnc-ssh",
    remmina_plugin_vnci_basic_settings,
    remmina_plugin_vnc_advanced_settings,
    REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL,
    remmina_plugin_vnc_features,

    remmina_plugin_vnc_init,
    remmina_plugin_vnc_open_connection,
    remmina_plugin_vnc_close_connection,
    remmina_plugin_vnc_query_feature,
    remmina_plugin_vnc_call_feature
};

G_MODULE_EXPORT gboolean
remmina_plugin_entry (RemminaPluginService *service)
{
    remmina_plugin_service = service;

    bindtextdomain (GETTEXT_PACKAGE, REMMINA_LOCALEDIR);
    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");

    if (! service->register_plugin ((RemminaPlugin *) &remmina_plugin_vnc))
    {
        return FALSE;
    }

    if (! service->register_plugin ((RemminaPlugin *) &remmina_plugin_vnci))
    {
        return FALSE;
    }

    return TRUE;
}

